--[[
        Class: Vector

        API :
        ---- functions ----
        VectorType(v)                           -- return if as vector
        VectorIntersection(a1,b1,a2,b2)         -- return the Intersection of 2 lines
        VectorDirection(v1,v2,v)
        VectorPointProjectionOnLine(v1, v2, v)  -- return a vector on line v1-v2 closest to v
        Vector(a,b,c)                           -- return a vector from x,y,z pos or from another vector

        ---- Vector Members ----
        x
        y
        z

        ---- Vector Functions ----
        vector:clone()                          -- return a new Vector from vector
        vector:unpack()                         -- x, z
        vector:len2()                           -- return vector^2
        vector:len2(v)                          -- return vector^v
        vector:len()                            -- return vector length
        vector:dist(v)                          -- distance between 2 vectors (v and vector)
        vector:normalize()                      -- normalize vector
        vector:rotate(phiX, phiY, phiZ)         -- rotate the vector by phi angle
        vector:rotated(phiX, phiY, phiZ)        -- return a new Vector rotate from vector by phi angle
        vector:projectOn(v)                     -- return a new Vector from vector projected on v
        vector:mirrorOn(v)                      -- return a new Vector from vector mirrored on v
        vector:center(v)                        -- return center between vector and v
        vector:crossP()                         -- return cross product of vector
        vector:dotP()                           -- return dot product of vector

        vector:polar()                          -- return the angle from axe
        vector:angleBetween(v1, v2)             -- return the angle formed from vector to v1,v2
        vector:compare(v)                       -- compare vector and v
        vector:perpendicular()                  -- return new Vector rotated 90� rigth
        vector:perpendicular2()                 -- return new Vector rotated 90� left
]]

-- STAND ALONE FUNCTIONS
function VectorType(v) --ok
    return (v ~= nil and (type(v) == "vector" or (type(v.x) == "number" and (type(v.y) == "number" or type(v.z) == "number"))))
end

function VectorIntersection(a1, b1, a2, b2) --not checked
    assert(VectorType(a1) and VectorType(b1) and VectorType(a2) and VectorType(b2), "VectorIntersection: wrong argument types (4 <vector> expected)")
    if math.close(b1.x, 0) and math.close(b2.z, 0) then return Vector(a1.x, a2.z) end
    if math.close(b1.z, 0) and math.close(b2.x, 0) then return Vector(a2.x, a1.z) end
    local m1 = (not math.close(b1.x, 0)) and b1.z / b1.x or 0
    local m2 = (not math.close(b2.x, 0)) and b2.z / b2.x or 0
    if math.close(m1, m2) then return nil end
    local c1 = a1.z - m1 * a1.x
    local c2 = a2.z - m2 * a2.x
    local ix = (c2 - c1) / (m1 - m2)
    local iy = m1 * ix + c1
    if math.close(b1.x, 0) then return Vector(a1.x, a1.x * m2 + c2) end
    if math.close(b2.x, 0) then return Vector(a2.x, a2.x * m1 + c1) end
    return Vector(ix, iy)
end

function VectorDirection(v1, v2, v) --not sure why you would need that
    assert(VectorType(v1) and VectorType(v2) and VectorType(v), "VectorDirection: wrong argument types (3 <vector> expected)")
    return (v1.x - v2.x) * (v.z - v2.z) - (v.x - v2.x) * (v1.z - v2.z)
end

function VectorPointProjectionOnLine(v1, v2, v)
    assert(VectorType(v1) and VectorType(v2) and VectorType(v), "VectorPointProjectionOnLine: wrong argument types (3 <vector> expected)")
    local line = Vector(v2) - v1
    local t = ((-(v1.x * line.x - line.x * v.x + (v1.z - v.z) * line.z)) / line:len2())
    return (line * t) + v1
end

class'Vector'

-- INSTANCED FUNCTIONS
function Vector:__init(a, b, c) --redone because i think its better to have vectors with "real" two numbers (and not the third one 0)
    if type(a) == table then
        assert(VectorType(a), "Vector: wrong argument types (<vector> or 2 <number> or 3 <number>)")
        self.x, self.y, self.z = a.x, a.y, a.z
    else
        assert(type(a) == "number" and (type(b) == "number" or type(c) == "number"), "Vector: wrong argument types (<vector> or 2 <number> or 3 <number>)")
        if type(a) == "number" then self.x = a.x end
        if type(b) == "number" then self.x = a.y end
        if type(c) == "number" then self.x = a.z end
    end
end

function Vector:__type() --types in lua are always in lowercase
    return "vector"
end

function Vector:__add(v) --redone (check if y or z exist)
    assert(VectorType(v) and VectorType(self), "add: wrong argument types (<vector> expected)")
    return Vector(self.x + v.x, (v.y and self.y) and self.y + v.y, (v.z and self.z) and self.z + v.z)
end

function Vector:__sub(v) --redone (check if y or z exist)
    assert(VectorType(v) and VectorType(self), "Sub: wrong argument types (<vector> expected)")
    return Vector(self.x - v.x, (v.y and self.y) and self.y - v.y, (v.z and self.z) and self.z - v.z)
end

function Vector.__mul(a, b) --redone, there was a calculation missing (you couldn't calculate 2*Vector(1,1,1) for example); dotp added
    if type(a) == "number" and type(b) == "vector" then
        return Vector({ x = b.x * a, y = b.y and b.y * a, z = b.z and b.z * a })
    elseif type(b) == "number" and type(a) == "vector" then
        return Vector({ x = a.x * b, y = a.y and a.y * b, z = a.z and a.z * b })
    else
        assert(VectorType(a) and VectorType(b), "Mul: wrong argument types (<vector> or <number> expected)")
        return a:dotP(b)
    end
end

function Vector.__div(a, b) --redone, same as __mul
    if type(a) == "number" and type(b) == "vector" then
        return Vector({ x = a / b.x, y = b.y and a / b.y, z = b.z and a / b.z })
    else
        assert(VectorType(a) and type(b) == "number", "Div: wrong argument types (<number> expected)")
        return Vector({ x = a.x / b, y = a.y and a.y / b, z = a.z and a.z / b })
    end
end

function Vector.__lt(a, b) --redone, now comparison of length (there is no other valid comparison possible with vectors)
    assert(VectorType(a) and VectorType(b), "__lt: wrong argument types (<vector> expected)")
    return a:Length() < b:Length()
end

function Vector.__le(a, b) --redone, same as __lt
    assert(VectorType(a) and VectorType(b), "__le: wrong argument types (<vector> expected)")
    return a:Length() <= b:Length()
end

function Vector:__eq(v) --redone, now its right
    assert(VectorType(v), "__eq: wrong argument types (<vector> expected)")
    return self.x == v.x and self.y == v.y and self.z == v.z
end

function Vector:__unm() --redone, added check for y and z
    return Vector(-self.x, self.y and -self.y, self.z and -self.z)
end

function Vector:__vector(v) --this is CrossP, how do you execute this function?
    assert(VectorType(v), "__vector: wrong argument types (<vector> expected)")
    local x = self.y * (v.z or 1) - self.z * (v.y or 1) --x, y and z are not used?!
    local y = self.z * v.x - self.x * (v.z or 1)
    local z = self.x * (v.y or 1) - self.y * v.x
    return Vector(self.y * (v.z or 1) - self.z * (v.y or 1), self.z * v.x - self.x * (v.z or 1), self.x * (v.y or 1) - self.y * v.x)
end

function Vector:__tostring() --redone
    if self.y and self.x then
        return "(" .. self.x .. "," .. self.y .. "," .. self.z")"
    else
        return "(" .. self.x .. "," .. self.y or self.z .. ")"
    end
end

function Vector:clone()
    return Vector(self)
end

function Vector:unpack()
    return self.x, self.y, self.z
end

function Vector:len2(v)
    assert(v == nil or VectorType(v), "dist: wrong argument types (<vector> expected)")
    local v = v and Vector(v) or self
    return self.x * v.x + self.y * v.y + self.z * v.z
end

function Vector:len()
    return math.sqrt(self:len2())
end

function Vector:dist(v)
    assert(VectorType(v), "dist: wrong argument types (<vector> expected)")
    local a = self - v
    return a:len()
end

function Vector:normalize() --you don't need two functions for that... that's all you need!
    return self / self:len()
end

function Vector:center(v) --ok
    assert(VectorType(v), "center: wrong argument types (<vector> expected)")
    return Vector((self + v) / 2)
end

function Vector:crossP(other) --added (Already there in another fashion, see __vector)
    assert(self.y and self.z and other.y and other.z, "crossP: wrong argument types (3 Dimensional <vector> expected)")
    return Vector({
        x = other.z * self.y - other.y * self.z,
        y = other.x * self.z - other.z * self.x,
        z = other.y * self.x - other.x * self.y
    })
end

function Vector:dotP(other) --added
    assert(VectorType(other), "dotP: wrong argument types (<vector> expected)")
    return self.x * other.x + (self.y and (self.y * other.y) or 0) + (self.z and (self.z * other.z) or 0)
end

function Vector:projectOn(v)
    assert(VectorType(v), "projectOn: invalid argument: cannot project Vector on " .. type(v))
    if type(v) ~= "Vector" then v = Vector(v) end
    local s = self:len2(v) / v:len2()
    return Vector(v * s)
end

function Vector:mirrorOn(v)
    assert(VectorType(v), "mirrorOn: invalid argument: cannot mirror Vector on " .. type(v))
    return self:projectOn(v) * 2
end

function Vector:sin(v)
    assert(VectorType(v), "sin: wrong argument types (<vector> expected)")
    if type(v) ~= "Vector" then v = Vector(v) end
    local a = self:__vector(v)
    return math.sqrt(a:len2() / (self:len2() * v:len2()))
end

function Vector:cos(v)
    assert(VectorType(v), "cos: wrong argument types (<vector> expected)")
    if type(v) ~= "Vector" then v = Vector(v) end
    return self:len2(v) / math.sqrt(self:len2() * v:len2())
end

function Vector:angle(v)
    assert(VectorType(v), "angle: wrong argument types (<vector> expected)")
    return math.acos(self:cos(v))
end

function Vector:affineArea(v)
    assert(VectorType(v), "affineArea: wrong argument types (<vector> expected)")
    if type(v) ~= "Vector" then v = Vector(v) end
    local a = self:__vector(v)
    return math.sqrt(a:len2())
end

function Vector:triangleArea(v)
    assert(VectorType(v), "triangleArea: wrong argument types (<vector> expected)")
    return self:affineArea(v) / 2
end

function Vector:rotateXaxis(phi)
    assert(type(phi) == "number", "Rotate: wrong argument types (expected <number> for phi)")
    local c, s = math.cos(phi), math.sin(phi)
    self.y, self.z = self.y * c - self.z * s, self.z * c + self.y * s
end

function Vector:rotateYaxis(phi)
    assert(type(phi) == "number", "Rotate: wrong argument types (expected <number> for phi)")
    local c, s = math.cos(phi), math.sin(phi)
    self.x, self.z = self.x * c + self.z * s, self.z * c - self.x * s
end

function Vector:rotateZaxis(phi)
    assert(type(phi) == "number", "Rotate: wrong argument types (expected <number> for phi)")
    local c, s = math.cos(phi), math.sin(phi)
    self.x, self.y = self.x * c - self.z * s, self.y * c + self.x * s
end

-- TODO
function Vector:rotate(phiX, phiY, phiZ)
    assert(type(phiX) == "number" and type(phiY) == "number" and type(phiZ) == "number", "Rotate: wrong argument types (expected <number> for phi)")
    if phiX ~= 0 then self:rotateXaxis(phiX) end
    if phiY ~= 0 then self:rotateYaxis(phiY) end
    if phiZ ~= 0 then self:rotateZaxis(phiZ) end
end

function Vector:rotated(phiX, phiY, phiZ)
    assert(type(phiX) == "number" and type(phiY) == "number" and type(phiZ) == "number", "Rotated: wrong argument types (expected <number> for phi)")
    local a = self:clone()
    a:rotate(phiX, phiY, phiZ)
    return a
end




-- not yet full 3D functions

function Vector:polar()
    if math.close(self.x, 0) then
        if self.z > 0 then return 90
        elseif self.z < 0 then return 270
        else return 0
        end
    else
        local theta = math.deg(math.atan(self.z / self.x))
        if self.x < 0 then theta = theta + 180 end
        if theta < 0 then theta = theta + 360 end
        return theta
    end
end

function Vector:angleBetween(v1, v2)
    assert(VectorType(v1) and VectorType(v2), "angleBetween: wrong argument types (2 <vector> expected)")
    local p1, p2 = (-self + v1), (-self + v2)
    local theta = p1:polar() - p2:polar()
    if theta < 0 then theta = theta + 360 end
    if theta > 180 then theta = 360 - theta end
    return theta
end

function Vector:compare(v)
    assert(VectorType(v), "compare: wrong argument types (<vector> expected)")
    local ret = self.x - v.x
    if ret == 0 then ret = self.z - v.z end
    return ret
end

function Vector:perpendicular()
    return Vector(-self.z, self.y, self.x)
end

function Vector:perpendicular2()
    return Vector(self.z, self.y, -self.x)
end
